#include "plotview.h"

#include <math.h>
#include <cmath>
#include <QSvgGenerator>
#include <QWheelEvent>
#include <QPaintEvent>
#include <QResizeEvent>
#include <QList>
#include <QPixmap>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QFile>
#include <QApplication>

#include "analitza/analitza.h"
#include "functionsmodel.h"

using namespace std;

QColor const PlotView::m_axeColor(100,100,255);
QColor const PlotView::m_axe2Color(235,235,235);
QColor const PlotView::m_derivativeColor(90,90,160);

PlotView::PlotView(FunctionsModel* fm ,QWidget * parent ):QWidget(parent),m_model(fm),
        valid(false), mode(None), m_squares(true), m_keepRatio(true), resolucio(800),
        m_framed(false), m_readonly(false), m_posText()
{
    this->setFocusPolicy(Qt::ClickFocus);
    this->setCursor(Qt::CrossCursor);

    this->setMouseTracking(!m_readonly);
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    setViewport(QRectF(QPointF(-12., 10.), QSizeF(24., -20.)));
    defViewport = userViewport;
    this->setAutoFillBackground(true);
    this->setBackgroundRole(QPalette::BrightText);

    connect(m_model, SIGNAL(dataChanged( const QModelIndex&, const QModelIndex& )),
        this, SLOT(update(const QModelIndex&, const QModelIndex)));
    connect(m_model, SIGNAL( rowsInserted ( const QModelIndex &, int, int ) ),
        this, SLOT(addFuncs(const QModelIndex&, int, int)));
    connect(m_model, SIGNAL( rowsRemoved ( const QModelIndex &, int, int ) ),
        this, SLOT(removeFuncs(const QModelIndex&, int, int)));

}
PlotView::~PlotView(){}

bool PlotView::toImage(const QString& path)
{
    bool b=false;

    if(!path.isEmpty() && path.endsWith(".svg")) {
        QFile f(path);
        QSvgGenerator gen;
        gen.setOutputDevice(&f);
        gen.setSize(this->size());
        pintafunc(&gen);
        b=true;
        forceRepaint();
    } else if(!path.isEmpty() && path.endsWith(".png")) {
        this->repaint();
        b=buffer.save(path, "PNG");
    }

    return b;
}
void PlotView::unselect()
{
    m_model->unselect();
}

void PlotView::setViewport(const QRectF& vp)
{
    userViewport = vp;
        if(userViewport.top()<userViewport.bottom()) {
                double aux = userViewport.bottom();
                userViewport.setBottom(userViewport.top());
                userViewport.setTop(aux);
        }

        if(userViewport.right()<userViewport.left()) {
                double aux = userViewport .left();
                userViewport.setLeft(userViewport .right());
                userViewport.setRight(aux);
        }

        sendStatus(QString("(%1, %2)-(%3, %4)")
                        .arg(viewport.left()).arg(viewport.top()).arg(viewport.right()).arg(viewport.bottom()));
        updateScale();
}

void PlotView::setResolution(int res)
{
    resolucio = res;
}

void PlotView::setReadOnly(bool ro)
{
    m_readonly=ro;
    this->setCursor(ro ? Qt::ArrowCursor : Qt::CrossCursor);
    setMouseTracking(!ro);
}

void PlotView::setKeepAspectRatio(bool ar)
{
    m_keepRatio=ar;
}

void PlotView::zoomIn()
{
    if(userViewport.height() < -3. && userViewport.width() > 3.){
        setViewport(QRectF(userViewport.left() +1., userViewport.top() -1.,
                          userViewport.width() -2., userViewport.height() +2.));
        updateScale();
    }
}

void PlotView::zoomOut()
{
    setViewport(QRectF(userViewport.left() -1., userViewport.top() +1.,
                      userViewport.width() +2., userViewport.height() -2.));
    updateScale();
}

void PlotView::update( const QModelIndex & startIdx, const QModelIndex & endIdx )
{
    Q_ASSERT(startIdx.isValid() && endIdx.isValid());
    int start=startIdx.row(), end=endIdx.row();

    for(int i=start; i<=end; i++) {
        m_model->updatePoints(i, toBiggerRect(viewport), static_cast<int>(floor(resolucio)));
    }
    valid=false;
    repaint();
}

void PlotView::addFuncs( const QModelIndex & parent, int start, int end)
{
    for(int i=start; i<=end; i++) {
                m_model->updatePoints(i, toBiggerRect(viewport), static_cast<int>(floor(resolucio)));
        }
        valid=false;
}

void PlotView::removeFuncs( const QModelIndex & parent, int start, int end)
{
    valid=false;
        repaint();
}

void PlotView::drawAxes(QPainter *f, function::Axe a)
{

    f->setRenderHint(QPainter::Antialiasing, false);

        switch(a) {
                case function::Polar:
                        drawPolarAxes(f);
                        break;
                default:
                        drawCartesianAxes(f);
        }

        //write coords
        f->drawText(QPointF(3.+this->width()/2., 13.               ), QString::number(viewport.top()));
        f->drawText(QPointF(3.+this->width()/2., this->height()-5. ), QString::number(viewport.bottom()));
        f->drawText(QPointF(8.                 , this->height()/2.-5.), QString::number(viewport.left()));
        f->drawText(QPointF(this->width()-30.  , this->height()/2.-5.),QString::number(viewport.right()));
        //EO write coords
}

void PlotView::drawPolarAxes(QPainter *w)
{
    QPen ceixos;
    ceixos.setColor(m_axeColor);
    w->setPen(ceixos);
    const QPointF center = toWidget(QPointF(0.,0.));
    bool zero=false;
    zero= center.x()>0. && center.y()>0. && center.x()<width() && center.y()<height();
    double thmin = zero ? 0. : fmin(floor(fmin(viewport.left(), viewport.bottom())), ceil(fmax(viewport.right(), viewport.top())));
    double thmax = ceil(fmax(
            sqrt(pow(viewport.topRight().x(), qreal(2)) + pow(viewport.topRight().y(), qreal(2))),
            sqrt(pow(viewport.bottomLeft().x(), qreal(2))+ pow(viewport.bottomLeft().y(), qreal(2)))
            )
                        );

    ceixos.setColor(m_axe2Color);
    ceixos.setStyle(Qt::SolidLine);
    w->setPen(ceixos);

    w->setRenderHint(QPainter::Antialiasing, true);
    for(double i=thmin; i<thmax; i++) { //i is +
        QPointF p(toWidget(QPointF(i,i)));
        QPointF p2(toWidget(QPointF(-i,-i)));
        w->drawEllipse(QRectF(p.x(),p.y(), p2.x()-p.x(),p2.y()-p.y()));
    }
    w->setRenderHint(QPainter::Antialiasing, false);

    ceixos.setColor(m_axeColor);
    ceixos.setStyle(Qt::SolidLine);
    w->setPen(ceixos);
    w->drawLine(QPointF(0., center.y()), QPointF(this->width(), center.y()));
    w->drawLine(QPointF(center.x(), 0.), QPointF(center.x(),this->height()));
}

void PlotView::drawCartesianAxes(QPainter *finestra)
{
    QPen ceixos;
    const QPointF center = toWidget(QPointF(0.,0.));
    QPointF p;
    ceixos.setColor(m_axe2Color);
    ceixos.setStyle(Qt::SolidLine);
    finestra->setPen(ceixos);

    double xini=ceil(viewport.left()), inc=1.;
    double yini=ceil(viewport.top());

    if(viewport.width()>100.) { //Draw less lines on large viewports
        inc=10.;
        xini=floor(xini/10.)*10.;
        yini=floor(yini/10.)*10.;
    }

    for(double x=xini; x<=viewport.right(); x+=inc) {	// ticks X
        p = toWidget(QPointF(x, 0.));
        if(m_squares)
        {
            finestra->drawLine(QPointF(p.x(), this->height()), QPointF(p.x(), 0.));
        }
        else
            finestra->drawLine(p, p+QPointF(0.,-3.));
    }

    for(double y=yini; y>=viewport.bottom(); y-=inc) {		// ticks y
        p = toWidget(QPointF(0., y));
        if(m_squares)
            finestra->drawLine(QPointF(0., p.y()), QPointF(width(), p.y()));
        else
            finestra->drawLine(p, p+QPointF(3.,0.));
    }

    ceixos.setColor(m_axeColor);
    ceixos.setStyle(Qt::SolidLine);
    finestra->setPen(ceixos);

    QPointF Xright(this->width(), center.y());
    QPointF Ytop(center.x(), 0.);

        //draw viewport axes
    finestra->drawLine(QPointF(0., center.y()), Xright);
    finestra->drawLine(Ytop, QPointF(center.x(),this->height()));
        //EO draw viewport axes
    finestra->setRenderHint(QPainter::Antialiasing, true);
    finestra->setBrush(m_axeColor);

    const double width=15., height=4.;
    QPointF dpx(width, height);
    QPointF dpy(height, width);
    QRectF rectX(Xright+dpx, Xright-dpx);
    QRectF rectY(Ytop+dpy, Ytop-dpy);

    int startAngleX = 150*16;
    int startAngleY = 240*16;
    int spanAngle = 60*16;
    finestra->drawPie(rectX, startAngleX, spanAngle);
    finestra->drawPie(rectY, startAngleY, spanAngle);
}

QPointF PlotView::toWidget(const QPointF &p) const
{
    double left=-viewport.left(), top=-viewport.top();
    return QPointF((left + p.x()) * rang_x,  (top + p.y()) * rang_y);
}

QPointF PlotView::fromWidget(const QPointF& p) const
{
    double part_negativa_x = -viewport.left();
    double part_negativa_y = -viewport.top();
    return QPointF(p.x()/rang_x-part_negativa_x, p.y()/rang_y-part_negativa_y);
}

QPointF PlotView::toViewport(const QPoint& mv) const
{
    return QPointF(mv.x()/rang_x, mv.y()/rang_y);
}

QPointF PlotView::calcImage(const QPointF& ndp)
{
    return m_model->calcImage(ndp).first;
}

QLineF PlotView::slope(const QPointF& dp) const
{
    return m_model->slope(dp);
}

QLineF PlotView::toWidget(const QLineF &line) const
{
    return QLineF(toWidget(line.p1()), toWidget(line.p2()));
}
void PlotView::forceRepaint()
{
    valid=false;
    repaint();
}

            //events
void PlotView::paintEvent( QPaintEvent * ev )
{
    if(!valid)
    {
        pintafunc(&buffer);
    }

    QPainter finestra(this);
    finestra.drawPixmap(0,0,width(),height(), buffer);
    QPen ccursor;
    QPointF ultim;

    if(!m_readonly && mode==None)
    {
        ultim = toWidget(mark);

                //Draw derivative
        ccursor.setColor(m_derivativeColor);
        ccursor.setStyle(Qt::SolidLine);
        QLineF sl=slope(fromWidget(ultim));
        sl.translate(mark);

        finestra.setPen(ccursor);
        finestra.setRenderHint(QPainter::Antialiasing, true);
        if(!sl.isNull() && ! _isnan(sl.length()))
                finestra.drawLine(toWidget(sl));
        finestra.setRenderHint(QPainter::Antialiasing, false);
                //EOderivative

        ccursor.setColor(QColor(0xc0,0,0));
        ccursor.setStyle(Qt::SolidLine);

        finestra.setPen(ccursor);
        finestra.drawLine(QPointF(0.,ultim.y()), QPointF(this->width(), ultim.y()));
        finestra.drawLine(QPointF(ultim.x(),0.), QPointF(ultim.x(), this->height()));

        int w=finestra.fontMetrics().width(m_posText)+15, h=finestra.fontMetrics().height();

        if(ultim.x()+w > this->width())
                ultim.setX(this->width()-w);
        if(ultim.y()+h > this->height())
                ultim.setY(this->height()-h);
        if(ultim.y() < 0.)
                ultim.setY(0.);

        finestra.setPen(QPen(Qt::black));
        finestra.drawText(QPointF(ultim.x()+15., ultim.y()+15.), m_posText);

    }
    else if(!m_readonly && mode==Selection)
    {
        ccursor.setColor(QColor(0xc0,0,0));
        ccursor.setStyle(Qt::SolidLine);
        finestra.setPen(ccursor);
        finestra.setBrush(QColor(0xff,0xff, 0,0x90));
        finestra.drawRect(QRect(press,last));
    }

    if(m_framed)
    {
        QPoint p2=QPoint(this->width(), this->height());
        QPen bord(Qt::black);
        finestra.setPen(bord);
        finestra.drawRect(QRect(QPoint(0,0), p2-QPoint(2,2)));
    }
    finestra.end();
}

void PlotView::mousePressEvent(QMouseEvent *e)
{

    if(!m_readonly && (e->button()==Qt::LeftButton || e->button()==Qt::MidButton)) {

        last = press = e->pos();
        ant = toViewport(e->pos());
        this->setCursor(QCursor(Qt::PointingHandCursor));
        if(e->button()==Qt::LeftButton && e->modifiers()&Qt::ControlModifier)
            mode=Selection;
        else if(e->button()==Qt::MidButton || (e->button()==Qt::LeftButton))
            mode=Pan;
    }
}
void PlotView::mouseReleaseEvent(QMouseEvent *e)
{
    if(m_readonly)
        this->setCursor(Qt::ArrowCursor);
    else
        this->setCursor(Qt::CrossCursor);

    if(!m_readonly && mode==Selection) {
        QPointF pd = toViewport(e->pos())-toViewport(press);
        const double mindist = fmin(fabs(pd.x()), fabs(pd.y())), rate=7.;
        const double minrate = fmin(fabs(viewport.width()/rate), fabs(viewport.height()/rate));

        if(mindist >= minrate) //if selection is not very small
            setViewport(QRectF(fromWidget(press), QSizeF(pd.x(), pd.y())));
        else
            sendStatus(QString("Tầm nhìn được chọn quá nhỏ"));
    }
    mode = None;
    this->repaint();
}
void PlotView::mouseMoveEvent(QMouseEvent *e)
{
    mark=calcImage(fromWidget(e->pos()));

    if(!m_readonly && mode==Pan && ant != toViewport(e->pos())){
        QPointF rel = toViewport(e->pos() - press);
        viewport.moveLeft(viewport.left() - rel.x());
        viewport.moveTop(viewport.top() - rel.y());
        setViewport(viewport);

        press = e->pos();
    } else if(e->buttons()&Qt::LeftButton) {
        last = e->pos();
    } else if(e->buttons()==0)
        sendStatus(QString("x=%1 y=%2").arg(mark.x(),3,'f',2).arg(mark.y(),3,'f',2));

    this->repaint();
}
void PlotView::keyPressEvent(QKeyEvent * e )
{
    const double xstep=viewport.width()/12., ystep=viewport.height()/10.;

    switch(e->key()) {
    case Qt::Key_Right:
        userViewport.setLeft(viewport.left() +xstep);
        userViewport.setRight(viewport.right() +xstep);
        updateScale();
        break;
    case Qt::Key_Left:
        userViewport.setLeft(viewport.left() -xstep);
        userViewport.setRight(viewport.right() -xstep);
        updateScale();
        break;
    case Qt::Key_Down:
        userViewport.setTop(viewport.top() -ystep);
        userViewport.setBottom(viewport.bottom() -ystep);
        updateScale();
        break;
    case Qt::Key_Up:
        userViewport.setTop(viewport.top() +ystep);
        userViewport.setBottom(viewport.bottom() +ystep);
        updateScale();
        break;
    case Qt::Key_Minus:
        zoomOut();
        break;
    case Qt::Key_Plus:
        zoomIn();
        break;
    default:
        return;
    }
    forceRepaint();
}
void PlotView::wheelEvent(QWheelEvent *e)
{
    int d = e->delta()>0 ? -1 : 1;
        if(viewport.left()-d < 1 && viewport.top()+d > 1 && viewport.right()+d > 1 && viewport.bottom()-d < 1) {
                QRectF nv;
                nv.setLeft(viewport.left() - d);
                nv.setTop(viewport.top() + d);
                nv.setRight(viewport.right() + d);
                nv.setBottom(viewport.bottom() - d);

                setViewport(nv);
        }
        sendStatus(QString("(%1, %2)-(%3, %4)").arg(viewport.left()).
                   arg(viewport.top()).arg(viewport.right()).arg(viewport.bottom()));
}
void PlotView::resizeEvent(QResizeEvent *e)
{
    buffer=QPixmap(this->size());
        updateScale();
        repaint();
}

void PlotView::pintafunc(QPaintDevice* qpd)
{
    QPalette p=qApp->palette();
    QPen pfunc;
    if(buffer.isNull() || buffer.width()!=width() || buffer.height()!=height())
        buffer = QPixmap(this->width(), this->height());
    buffer.fill(p.color(QPalette::Active, QPalette::Base));

    pfunc.setColor(QColor(0,150,0));
    pfunc.setWidth(2);

    QPainter finestra;
    finestra.begin(qpd);
    finestra.setPen(pfunc);

    function::Axe t=function::Cartesian;
    if(m_model->hasSelection())
            t=m_model->currentFunction().axeType();
    drawAxes(&finestra, t);
    finestra.setRenderHint(QPainter::Antialiasing, true);

    int k=0;
    QRectF panorama(QPoint(0,0), size());
    FunctionsModel::const_iterator it=m_model->constBegin(), itEnd=m_model->constEnd();
    for (; it!=itEnd; ++it, ++k )
    {
        if(!it->isShown())
            continue;
        pfunc.setColor(it->color());
        pfunc.setWidth(m_model->isSelected(k)+1);
        finestra.setPen(pfunc);

        const QVector<QPointF> &vect=it->points();
        Q_ASSERT(!vect.isEmpty());
        QList<int> jumps=it->jumps();

        unsigned int pointsCount = vect.count();
        QPointF ultim(toWidget(vect[0]));

        int nextjump= jumps.isEmpty() ? -1 : jumps.takeFirst();
        for(unsigned int j=0; j<pointsCount; j++)
        {
            QPointF act=toWidget(vect.at(j));
            if(isinf(act.y()) && !isnan(act.y())) qDebug() << "toma ya";
            else if(isinf(act.y()) && isnan(act.y())) qDebug() << "joooooooooooo";

            if(!isnan(act.y()) && !isnan(ultim.y()) && nextjump!=int(j)) {
                finestra.drawLine(ultim, act);

#if 0
                QPen p(Qt::red);
                p.setWidth(3);
                finestra.setPen(p);
                finestra.drawPoint(ultim);
                finestra.setPen(pfunc);
#endif
            } else if(nextjump==int(j))
            {
                do
                {
                    if(nextjump!=int(j))
                        finestra.drawPoint(act);
                    nextjump=jumps.isEmpty() ? -1 : jumps.takeFirst();
                }
                while(!jumps.isEmpty() && jumps.first()==nextjump+1);
#if 0
                qDebug() << "jumpiiiiiing" << vect.at(j);
                QPen p(Qt::blue);
                p.setWidth(2);
                finestra.setPen(p);
                finestra.drawLine(QLineF(QPointF(act.x(), height()/2-10), QPointF(act.x(), height()/2+10)));
                finestra.setPen(pfunc);
#endif
            }

            ultim=act;
        }
    }

    finestra.end();
    valid=true;
}
void PlotView::updateScale()
{
    viewport=userViewport;
    rang_x= width()/viewport.width();
    rang_y= height()/viewport.height();

    if(m_keepRatio && rang_x!=rang_y)
    {
        rang_y=rang_x=qMax(fabs(rang_x), fabs(rang_y));
        if(rang_y>0.) rang_y=-rang_y;
        if(rang_x<0.) rang_x=-rang_x;

        double newW=width()/rang_x, newH=height()/rang_x;

        double mx=(userViewport.width()-newW)/2.;
        double my=(userViewport.height()-newH)/2.;

        viewport.setLeft(userViewport.left()+mx);
        viewport.setTop(userViewport.bottom()-my);
        viewport.setWidth(newW);
        viewport.setHeight(-newH);
    }

    if(m_model->rowCount())
        update(m_model->index(0,0), m_model->index(m_model->rowCount()-1,0));
    forceRepaint();
}


QRect PlotView::toBiggerRect(const QRectF& ent)
{
    QRect ret;
    ret.setTop(static_cast<int>(ceil(ent.top())));
    ret.setBottom(static_cast<int>(floor(ent.bottom())));
    ret.setLeft(static_cast<int>(floor(ent.left())));
    ret.setRight(static_cast<int>(ceil(ent.right())));

    return ret;
}
void PlotView::resetViewport()
{
    setViewport(defViewport);
}
